Skip to content

Proposal/teacher onboarding mvp - #595

Open
CarlyAThomas wants to merge 18 commits into
freeCodeCamp:mainfrom
CarlyAThomas:proposal/teacher-onboarding-mvp
Open

Proposal/teacher onboarding mvp#595
CarlyAThomas wants to merge 18 commits into
freeCodeCamp:mainfrom
CarlyAThomas:proposal/teacher-onboarding-mvp

Conversation

@CarlyAThomas

@CarlyAThomas CarlyAThomas commented May 18, 2026

Copy link
Copy Markdown
Contributor

Checklist:

This PR is not associated with a specific issue

Case Study (Teacher Invite Acceptance Flow)

  1. Admin creates/resends a teacher invite from the admin dashboard.
  2. System sends a secure email link: /teacher/invite/<token>.
  3. Invited teacher signs in with the invited email.
  4. Teacher accepts invite.
  5. Invitation becomes ACCEPTED and user role is set to TEACHER.

What Changed (and Why)

Validation

  • npm run lint:code
  • npm test

How to Test

Prerequisites
This branch allows you to create an ADMIN account and send out email invitations to users to onboard as teachers.
You need:

  • 1 "Admin" email (to serve as your ADMIN, and set up your Classroom account)
  • 1+ extra "Teacher" emails (to receive email invites and onboard as teachers)

Configuring this branch for local development

  1. Create a local branch that tracks the CarlyAThomas:proposal/teacher-onboarding-mvp branch.
  2. Run cp .env.development.example .env.development
  3. Update SMTP_USER in .env.development with your Admin Email.
  4. Go to myaccount.google.com/apppasswords. Make sure you're using your ADMIN email for steps 3-6.
  5. Type in the name of the Application (FCC Classroom) and select create.
  6. Google generates a 16-character password
  7. Copy that password (not your Gmail password)
  8. Update SMTP_PASS in .env.development with the new password.

Running the app

  1. In the terminal, run npx prisma migrate reset, and press y to reset your database.
  2. Run npm run develop and npx prisma studio to run the project.
  3. Log in with your "Admin" email.
  4. In prisma studio, set your Admin account's role to ADMIN.

Testing

  1. Go to the /admin page
  2. Type out your TEACHER email address, then press Enter.
  3. Open an Incognito tab, or a different browser, so you can log into a different account.
  4. Go to your TEACHER email address and onboard using the link provided. (Use Case: Onboarding a Teacher with a brand new account)
  5. (Alternative) Open localhost:3001 again and create a Classroom account with your TEACHER email address. Then, repeat step 4. (Use Case: Onboarding a Teacher who already has an account)

@CarlyAThomas
CarlyAThomas requested a review from a team as a code owner May 18, 2026 22:40
@socket-security

socket-security Bot commented May 18, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednodemailer@​6.10.196729794100

View full report

@utsab utsab left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this PR is a solid end-to-end workflow for the teacher invite. Nice work.

Can you add some tests? Given that there are so many status change branches (pending, accepted, rejected, expired), it would be good to have a test that verifies that each status is assigned correctly. You can mock the 3rd party apps (like the SMTP server).

You may also want to add a test to confirm that an uninvited teacher cannot access the classes page.

"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "StudentInvitation_pkey" PRIMARY KEY ("studentInvitationId")
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StudentInvitation schema changes don't seem like they belong in this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — removed. StudentInvitation was leftover from an earlier, separate design for student invites that never shipped (student onboarding ended up going a different route in #600, no invite model at all yet so I am excluding it from here since it can easily be added later based on the teacher onboarding design if we want it). Deleted the model, its relations, and squashed the migrations back into a single initial_setup since we're still pre-release and don't need incremental migration history yet.

Comment on lines +79 to +81
const handleSignIn = async () => {
await signIn('auth0');
};

@utsab utsab Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are hardcoding auth0 which means a contributor using the Github oAuth would not be able to test this feature locally. I'm not totally opposed to only supporting auth0, but I'm curious, how difficult/complicated would it be to support both auth0 and Github oAuth?

If we do decide to only support auth0, then we should document somewhere in the README that if a developer decides to go with Github oAuth, certain features (like Teacher Invitations) won't work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a really good catch and I appreciate it. It was hardcoded because the GitHub OAuth has not been functional yet but it was easy to make agnostic. I switched to signIn(null, { callbackUrl: router.asPath }), matching the provider-agnostic pattern already used in components/authButton.js. This respects whichever provider(s) are configured (Auth0 and/or GitHub via GITHUB_OAUTH_PROVIDER_ENABLED), so contributors testing locally with GitHub OAuth aren't blocked on this page anymore, and if the GitHub OAuth is fixed this will already accommodate it.

Comment thread README.md
- No port conflicts occur because domains are different
- The port changes in this repository are primarily for local development

### Teacher Invitation Email Setup

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation is great, but I'm concerned the README.md is getting too long. Can you create a subpage for this documentation and link it from the README?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a separate issue & PR for this! Issue #593 and PR #605

CarlyAThomas and others added 17 commits September 2, 2026 14:48
…ity signature

- Convert pages/error.js and pages/teacher/invite/[inviteToken].js to modern Next.js Link pattern (no nested anchors)
- Refactor util/inviteEmail.js to accept object params {invitedTeacherEmail, inviteUrl, expiresAt, invitedByEmail}
- Ensures proper Nodemailer integration for teacher invitation emails
- redesign invite acceptance page with FCC-aligned styling
- update teacher invitation email content and branding hierarchy
- fix teacher invite post-accept redirect by role
- improve revoke API message for inactive invites
- add README testing/setup notes for SMTP invite flow
…tion

The StudentInvitation model/relations were leftover scaffolding from an
earlier, superseded student-invite design and had no consumers anywhere
in this branch (student onboarding shipped via a different, join-link
based flow in freeCodeCamp#600). Per review feedback, they don't belong in the
teacher invitation PR.

Also collapsed the two migrations back into a single initial_setup
migration, regenerated via `prisma migrate dev` against a local dev
database, since the project is still pre-release and doesn't need
incremental migration history yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
signIn('auth0') skipped NextAuth's provider selection and always
launched Auth0's flow, even for contributors running locally with
GITHUB_OAUTH_PROVIDER_ENABLED=true and no Auth0 credentials. Switch to
signIn(null, { callbackUrl }), matching the provider-agnostic pattern
already used in components/authButton.js, and keep the user on the
invite-accept page after sign-in instead of redirecting to '/'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds tests for pages/api/teacher_invites/accept.js,
pages/api/admin/teacher_invites/{resend,revoke}.js covering each status
branch (PENDING valid/expired, already ACCEPTED by self/other, and
REVOKED/CANCELLED/EXPIRED rejection), per review feedback asking for
coverage of each invitation status. Prisma, next-auth's session lookup,
and outbound email are all mocked - no real database or SMTP calls.

Also documents current role-promotion behavior on accept: ADMIN is
protected and stays ADMIN, while TEACHER/STUDENT/NONE are all
unconditionally promoted to TEACHER (single-role field, no student
guard needed since classroom membership is just a roster entry).

Adds direct unit tests for areEquivalentInviteEmails' Gmail-style
email normalization (dot/plus stripping), previously untested.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@CarlyAThomas
CarlyAThomas force-pushed the proposal/teacher-onboarding-mvp branch from fca2eec to a80a4c4 Compare September 3, 2026 00:21
pages/classes/index.js's getServerSideProps gate only checks a user's
current role - it has no notion of invitation history. That single
check is what covers every way an account can lack teacher access:
never invited, a teacher invite that was revoked or left to expire
before being accepted, or being demoted/removed after previously
being a teacher. Covers that gate: no session and non-TEACHER role
both redirect to /error, ADMIN redirects to /admin, and only an
accepted TEACHER reaches the page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@CarlyAThomas

CarlyAThomas commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

I added test coverage for both asks:

  • tests/api/teacherInvites.test.js covers every status branch on accept/resend/revoke: PENDING (valid + expired), already-ACCEPTED (by self and by a different user), and REVOKED/CANCELLED/EXPIRED rejection — plus the ADMIN-stays-ADMIN vs everyone-else-becomes-TEACHER role promotion behavior on accept.
  • tests/utils/inviteApiUtils.test.js covers the normalization checking for what a valid email is.
  • tests/pages/classes.test.js covers the /classes access gate: signed-out, non-TEACHER role (covers never-invited, revoked-before-accepting, and demoted-after-being-a-teacher, since the gate only checks current role), ADMIN redirect, and successful TEACHER access.

All mocked (Prisma, session, email) — no real DB/SMTP in the test run.

Hi @utsab it's ready for a re-review when you are available!

@NewtonLC
NewtonLC requested a review from utsab September 3, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants